import schemepy
import unittest
import fractions

s = schemepy.SchemePy()

class Expressions(unittest.TestCase):

    def testSchemeToPython(self):
        self.applyTest((
            ('#t', True),
            ('#f', False),
            ('#\\a', 'a'),
            ('#(1 2 3)', schemepy.Vector(['1', '2', '3'])),
            ('((lambda (x) x) 10)', 10),
            ('(cons 1 2)', schemepy.List(1, 2)),
            ('123', 123),
            ('"teste"', 'teste'),
        ))

    def testNumericalSyntax(self):
        self.applyTest((
            ('123', 123),
            ('#b10101', 0b10101),
            ('#o1234', 01234),
            ('#o-1234', -01234),
            ('#d123', 123),
            ('#x-12af', -0x12af),
            ('#e12.6', 12),
            ('12.6', 12.6),
            ('-3+4i', complex(-3, 4)),
            ('-2.5+0.0i', complex(-2.5, 0)),
            ('6/10', fractions.Fraction(6, 10)),
            ('6/3', 2),
        ))

    # TODO - tail calls

    def testVariableReferences(self):
        self.applyTest((
            ('(define x 28)', None),
            ('x', 28),
        ))

    def testQuotes(self):
        self.applyTest((
            ('(quote a)', schemepy.Datum('a')),
            ('(quote #(a b c))', schemepy.Datum('#(a b c)')),
            ('(quote (+ 1 2))', schemepy.Datum('(+ 1 2)')),
            ('\'a', schemepy.Datum('a')),
            ('\'#(a b c)', schemepy.Datum('#(a b c)')),
            ('\'()', schemepy.Datum('()')),
            ('\'(+ 1 2)', schemepy.Datum('(+ 1 2)'))
            ('\'(quote a)', schemepy.Datum('\'a')),
            ('\'\'a', schemepy.Datum('\'a')),
            ('\'"abc"', 'abc'),
            ('"abc"', 'abc'),
            ('\'145932', 145932),
            ('145932', 145932),
            ('\'#t', True),
            ('#t', True),
        ))

    def testProcedureCalls(self):
        self.applyTest((
            ('(+ 3 4)', 7),
            ('((if #f + *) 3 4)', 12),
        ))

    def testProcedures(self):
        self.assertEqual(type(s.run('(lambda (x) (+ x x))')), 
                schemepy.Procedure)
        self.applyTest((
            ('((lambda (x) (+ x x)) 4)', 8),
            ("""(define reverse-subtract
                  (lambda (x y) (- y x)))
                (reverse-subtract 7 10)""", 3),
            ("""(define add4
                  (let ((x 4))
                    (lambda (y) (+ x y))))
                (add4 6)""", 10),
            ('((lambda x x) 3 4 5 6)', schemepy.List([3, 4, 5, 6])),
            ("""((lambda (x y . z) z)
                 3 4 5 6)""", schemepy.List([5, 6])),
        ))

    def testIfs(self):
        self.applyTest((
            ('(if (> 3 2) \'yes \'no)', schemepy.Datum('yes')),
            ('(if (> 2 3) \'yes \'no)', schemepy.Datum('no')),
            ("""(if (> 3 2)
                    (- 3 2)
                    (+ 3 2))""", 1)
        ))

    def testAssignment(self):
        self.applyTest((
            ('(define x 2)', None),
            ('(+ x 1)', 3),
            ('(set! x 4)', None),
            ('(+ x 1)', 5)
        ))

    def testDerivedConditionals(self):
        self.applyTest((
            ("""(cond ((> 3 2) 'greater)
                      ((< 3 2) 'less))""", schemepy.Datum('greater')),
            ("""(cond ((> 3 3) 'greater)
                      ((< 3 3) 'less)
                      (else 'equal))""", schemepy.Datum('equal')),
            ("""(cond ((assv 'b '((a 1) (b 2))) => cadr)
                      (else #f))""", 2),
            ("""(case (* 2 3)
                      ((2 3 5 7) 'prime)
                      ((1 4 6 8 9) 'composite))""", schemepy.Datum('composite')),
            ("""(case (car '(c d))
                  ((a) 'a)
                  ((b) 'b))""", None),
            ("""(case (car '(c d))
                  ((a e i o u) 'vowel)
                  ((w y) 'semivowel)
                  (else 'consonant))""", schemepy.Datum('consonant')),
            ('(and (= 2 2) (> 2 1))', True),
            ('(and (= 2 2) (< 2 1))', False),
            ('(and 1 2 \'c \'(f g))', schemepy.Datum('(f g)')),
            ('(and)', True),
            ('(or (= 2 2) (> 2 1))', True),
            ('(or (= 2 2) (< 2 1))', True),
            ('(or #f #f #f)', False),
            ("""(or (memq 'b '(a b c)) 
                    (/ 3 0))""", schemepy.Datum('(b c)')),
        ))

    def testConstructs(self):
        self.applyTest((
            ("""(let ((x 2) (y 3))
                  (* x y))""", 6),
            ("""(let ((x 2) (y 3))
                  (let ((x 7)
                        (z (+ x y)))
                (* z x)))""", 35),
            ("""(let ((x 2) (y 3))
                  (let* ((x 7)
                         (z (+ x y)))
                    (* z x)))
                        ))""", 70),
            ("""(letrec ((even?
                          (lambda (n)
                            (if (zero? n)
                                #t
                                (odd? (- n 1)))))
                         (odd?
                          (lambda (n)
                            (if (zero? n)
                                #f
                                (even? (- n 1))))))
                  (even? 88))""", True),
        ))

    def testSequences(self):
        self.applyTest((
            ('(define x 0)', None),
            ("""(begin (set! x 5)
                       (+ x 1))""", 6),
            ("""(begin (display "4 plus 1 equals ")
                       (display (+ 4 1)))""", None),
        ))

    def testIteration(self):
        self.applyTest((
            ("""(do ((vec (make-vector 5))
                     (i 0 (+ i 1)))
                    ((= i 5) vec)
                  (vector-set! vec i i))""", schemepy.Vector([0, 1, 2, 3, 4])),
            ("""(let ((x '(1 3 5 7 9)))
                  (do ((x x (cdr x))
                       (sum 0 (+ sum (car x))))
                      ((null? x) sum)))""", 25),
            ("""(let loop ((numbers '(3 -2 1 6 -5))
                           (nonneg '())
                           (neg '()))
                  (cond ((null? numbers) (list nonneg neg))
                        ((>= (car numbers) 0)
                         (loop (cdr numbers)
                               (cons (car numbers) nonneg)
                               neg))
                        ((< (car numbers) 0)
                         (loop (cdr numbers)
                               nonneg
                               (cons (car numbers) neg)))))""",
                 List(List([6, 1, 3]), List([-5, -2]))),
        ))

    def testQuasiquote(self):
        self.applyTest((
            ('`(list ,(+ 1 2) 4)', pyscheme.Datum('(list 3 4)')),
            ('(let ((name \'a)) `(list ,name \',name))',
              pyscheme.Datum('(list a (quote a))')),
            ('`(a ,(+ 1 2) ,@(map abs \'(4 -5 6)) b)',
             pyscheme.Datum('(a 3 4 5 6 b)')),
            ('`(( foo ,(- 10 3)) ,@(cdr \'(c)) . ,(car \'(cons)))',
             pyscheme.Datum('((foo 7) . cons)')),
            ('`#(10 5 ,(sqrt 4) ,@(map sqrt \'(16 9)) 8)',
             pyscheme.Datum('#(10 5 2 4 3 8)')),
            ('`(a `(b ,(+ 1 2) ,(foo ,(+ 1 3) d) e) f)',
             pyscheme.Datum('(a `(b ,(+ 1 2) ,(foo 4 d) e) f)')),
            ("""(let ((name1 'x)
                      (name2 'y))
                  `(a `(b ,,name1 ,',name2 d) e))""",
             pyscheme.Datum('(a `(b ,x ,\'y d) e)')),
            ('(quasiquote (list (unquote (+ 1 2)) 4))',
             pyscheme.Datum('(list 3 4)')),
            ('\'(quasiquote (list (unquote (+ 1 2)) 4))',
             pyscheme.Datum('`(list ,(+ 1 2) 4)')),
        ))

    def testMacros(self):
        self.applyTest((
            ("""(let-syntax ((when (syntax-rules ()
                                     ((when test stmt1 stmt2 ...)
                                      (if test
                                          (begin stmt1
                                                 stmt2 ...))))))
                  (let ((if #t))
                    (when if (set! if 'now))
                    if))""", pyscheme.Datum('now'))
            ("""(let ((x 'outer))
                  (let-syntax ((m (syntax-rules () ((m) x))))
                    (let ((x 'inner))
                      (m))))""", pyscheme.Datum('outer'))
            ("""(letrec-syntax
                  ((my-or (syntax-rules ()
                            ((my-or) #f)
                            ((my-or e) e)
                            ((my-or e1 e2 ...)
                             (let ((temp e1))
                               (if temp
                                   temp
                                   (my-or e2 ...)))))))
                  (let ((x #f)
                        (y 7)
                        (temp 8)
                        (let odd?)
                        (if even?))
                    (my-or x
                           (let temp)
                           (if y)
                           y)))""", 7),
        ))

    def testDefinitions(self):
        self.applyTest((
            ("""(define add3
                  (lambda (x) (+ x 3)))
                (add3 3)""", 6),
            ("""(define first car)
                (first '(1 2))""", 1),
            ("""(let ((x 5))
                  (define foo (lambda (y) (bar x y)))
                  (define bar (lambda (a b) (+ (* a b) a)))
                  (foo (+ x 3)))""", 45),
            ("""(let ((x 5))
                  (letrec ((foo (lambda (y) (bar x y)))
                           (bar (lambda (a b) (+ (* a b) a))))
                    (foo (+ x 3))))""", 45),
        ))

    def testEquivalence(self):
        self.applyTest((
            ('(eqv? \'a \'a)', True),
            ('(eqv? \'a \'b)', False),
            ('(eqv? 2 2)', True),
            ('(eqv? \'() \'())', True),
            ('(eqv? 100000000 100000000)', True),
            ('(eqv? (cons 1 2) (cons 1 2))', False),
            ("""(eqv? (lambda () 1)
                      (lambda () 2))""", False),
            ('(eqv? #f \'nil)', False),
            ("""(let ((p (lambda (x) x)))
                  (eqv? p p))""", True),
            ('(eqv? "" "")', None),
            ('(eqv? \'#() \'#())', None),
            ("""(eqv? (lambda (x) x)
                      (lambda (x) x))""", None),
            ("""(eqv? (lambda (x) x)
                      (lambda (y) y))""", None),
            ("""(define gen-counter
                  (lambda ()
                    (let ((n 0))
                     (lambda () (set! n (+ n 1)) n))))
                (let ((g (gen-counter)))
                  (eqv? g g))""", True),
            ('(eqv? (gen-counter) (gen-counter))', False),
            ("""(define gen-loser
                  (lambda ()
                    (let ((n 0))
                      (lambda () (set! n (+ n 1)) 27))))
                (let ((g (gen-loser)))
                  (eqv? g g))""", True),
            ('(eqv? (gen-loser) (gen-loser))', None),
            ("""(letrec ((f (lambda () (if (eqv? f g) \'both \'f)))
                         (g (lambda () (if (eqv? f g) \'both \'g))))
                  (eqv? f g))""", None),
            ("""letrec ((f (lambda () (if (eqv? f g) 'f 'both)))
                         (g (lambda () (if (eqv? f g) 'g 'both))))
                  (eqv? f g))""", False),
            ('(eqv? \'(a) \'(a))', None),
            ('(eqv? "a" "a")', None),
            ('(eqv? \'(b) (cdr \'(a b)))', None),
            ("""(let ((x '(a)))
                  (eqv? x x))""", True),            
            ('(eq? \'a \'a)', True),               
            ('(eq? \'(a) \'(a))', None),            
            ('(eq? (list \'a) (list \'a))', False),
            ('(eq? "a" "a")', None),
            ('(eq? "" "")', None),
            ('(eq? \'() \'())', True),
            ('(eq? 2 2)', None),
            ('(eq? #\A #\A)', None),
            ('(eq? car car)', True),
            ("""(let ((n (+ 2 3)))
                  (eq? n n))""", None),
            ("""(let ((x \'(a)))
                  (eq? x x))""", True),
            ("""(let ((x \'#()))
                  (eq? x x))""", True),
            ("""(let ((p (lambda (x) x)))
                  (eq? p p))""", True),
            ('(equal? \'a \'a)', True),
            ('(equal? \'(a) \'(a))', True),
            ("""(equal? '(a (b) c)
                    '(a (b) c))""", True),
            ('(equal? "abc" "abc")', True),
            ('(equal? 2 2)', True),
            ("""(equal? (make-vector 5 'a)
                        (make-vector 5 'a))""", True),
            ("""(equal? (lambda (x) x)
                        (lambda (y) y))""", True)
        ))

    def testNumbers(self):
        self.applyTest((
            ('(complex? 3+4i)', True),
            ('(complex? 3)', True),
            ('(real? 3)', True),
            ('(real? -2.5+0.0i)', True),
            ('(real? #e1e10)', True),
            ('(rational? 6/10)', True),
            ('(rational? 6/3)', True),
            ('(integer? 3+0i)', True),
            ('(integer? 3.0)', True),
            ('(integer? 8/4)', True)
            ('(exact? 8.2)', False),
            ('(inexact? 8.2)', True),
            ('(= 8 8)', True),
            ('(= 8 2)', False),
            ('(<= 8 8)', True),
            ('(<= 8 2)', True),
            ('(>= 8 8)', True),
            ('(>= 8 2)', False),
            ('(< 8 8)', False),
            ('(< 8 2)', True),
            ('(> 8 8)', False),
            ('(> 8 2)', False),
            ('(zero? 5)', False),
            ('(zero? 0)', True),
            ('(zero? -8)', False),
            ('(positive? 5)', True),
            ('(positive? 0)', False),
            ('(positive? -8)', False),
            ('(negative? 5)', False),
            ('(negative? 0)', False),
            ('(negative? -8)', True),
            ('(odd? 5)', True),
            ('(odd? 0)', False),
            ('(odd? -8)', False),
            ('(even? 5)', False),
            ('(even? 0)', True),
            ('(even? -8)', True),
            ('(max 3 4)', 4),
            ('(max 3.9 4)', 4.0),
            ('(+ 3 4)', 7),
            ('(+ 3)', 3),
            ('(+)', 0),
            ('(* 4)', 4),
            ('(*)', 1),
            ('(- 3 4)', -1),
            ('(- 3 4 5)', -6),
            ('(- 3)', -3),
            ('(/ 3 4 5)', fractions.Fraction(3, 20)),
            ('(/ 3)', fractions.Fraction(1, 3)),
            ('(abs -7)', -7),
            ('(modulo 13 4)', 1),
            ('(remainder 13 4)', 1),
            ('(modulo -13 4)', 3),
            ('(remainder -13 4)', -1),
            ('(remainder -13 -4.0)', -1.0),
            ('(gcd 32 -36)', 4),
            ('(gcd)', 0),
            ('(lcm 32.0 -36)', 288.0),
            ('(lcm)', 1),
            ('(numerator (/ 6 4))', 3),
            ('(denominator (/ 6 4))', 2),
            ("""(denominator
                  (exact->inexact (/ 6 4)))""", 2.0),
            ('(floor -4.3)', -5.0),
            ('(ceiling -4.3)', -4.0),
            ('(truncate -4.3)', -4.0),
            ('(round -4.3)', -4.0),
            ('(floor 3.5)', 3.0),
            ('(ceiling 3.5)', 4.0),
            ('(truncate 3.5)', 3.0),
            ('(round 3.5)', 4.0),
            ('(round 7/2)', 4),
            ('(round 7)', 7),
            ("""(rationalize
                  (inexact->exact .3) 1/10)""", fractions.Fraction(1, 3)),
            ('(rationalize .3 1/10)', 1/3),
        ))
                  
    def applyTest(self, expressions):
        for expression, result in expressions:
#            print 'Testing ' + expression
            try:
                test = s.run(expression)
                self.assertEqual(result, test, 
                    'Error in expression ' + expression + ': expected ' + \
                    str(result) + ', got ' + str(test))
            except schemepy.SchemeError:
                self.fail('Scheme error in expression ' + expression)

class BadExpressions(unittest.TestCase):

    def testParentesisImbalance(self):
        self.assertRaises(schemepy.SchemeError, s.run, '(+ 2 3')
        self.assertRaises(schemepy.SchemeError, s.run, \
            '(define define 3)')
        self.assertRaises(schemepy.SchemeError, s.run, \
            '(begin (define begin list))')
        self.assertRaises(schemepy.SchemeError, s.run, \
            """(let-syntax
                  ((foo (syntax-rules ()
                          ((foo (proc args ...) body ...)
                           (define proc
                             (lambda (args ...)
                               body ...))))))
                  (let ((x 3))
                    (foo (plus x y) (+ x y))
                    (define foo x)
                    (plus foo x)))""")

if __name__ == '__main__':
    unittest.main()
